"""
## 2025-08-27 harrydeep@wardyan.top

从本地的一个文件夹里列出里面的sql文件:
获取待提交脚本的目录, 在本地指定目录内查询这样格式的文件夹: yyyyMMdd 开头的文件夹, 每个文件夹遍历检查.注意要排除掉所有带debug或者带bak,test的目录或者文件名,要不分大小写
获取文件夹的创建时间和最后修改时间, 以最后的一个时间为准, 只列出最近一个星期内创建或修改过的文件夹
获取目录下的所有文件
筛选出.sql文件
按文件内容推测这个sql文件的内容是哪个SQL对象:数据表?视图?存储过程?函数?
连接SQLSERVER的CHECKTOOL数据库(IP:192.168.0.248, 端口:1433, 用户:feng, 密码:0400923),按这个SQL对象检查历史推送记录
连接SQLSERVER的ERPDB4数据库(IP:192.168.0.248, 端口:1433, 用户:feng, 密码:0400923),测试执行是否报错
在共享目录:Y:\\创建当天的文件夹,命名格式:yyyyMMdd_FENG
把测试OK的sql文件复制到共享目录下刚才创建的目录下
把本地目录改名, 去掉最开始的年份里的前2位
报告结果
"""

import argparse
import re
import os
import chardet
import shutil
from datetime import datetime, timedelta
import ctypes
from ctypes import wintypes

def analyze_sql_file(file_path):
    """
    分析SQL文件，检查是否存在明显的语法错误或关键字错误
    
    Args:
        file_path (str): SQL文件的路径
    
    Returns:
        dict: 包含分析结果的字典
    """
    result = {
        'file_path': file_path,
        'is_valid': True,
        'errors': [],
        'sql_object_type': None,
        'object_name': None
    }
    
    try:
        # 读取SQL文件内容，自动检测编码
        content = read_file_with_encoding(file_path)
        
        # 移除注释（单行和多行）
        content_no_comments = remove_sql_comments(content)
        
        # 检测SQL对象类型
        sql_object_info = detect_sql_object_type(content_no_comments)
        result['sql_object_type'] = sql_object_info['type']
        result['object_name'] = sql_object_info['name']
        
        # 检查常见语法错误
        check_syntax_errors(content_no_comments, result)
        
        # 检查关键字使用错误
        check_keyword_errors(content_no_comments, result)
        
        # 检查基本结构完整性
        check_structure_integrity(content_no_comments, result)
        
    except FileNotFoundError:
        result['is_valid'] = False
        result['errors'].append(f"文件未找到: {file_path}")
    except Exception as e:
        result['is_valid'] = False
        result['errors'].append(f"分析文件时出错: {str(e)}")
    
    return result

def read_file_with_encoding(file_path):
    """
    读取文件内容，自动检测编码
    
    Args:
        file_path (str): 文件路径
    
    Returns:
        str: 文件内容
    """
    try:
        # 首先尝试以UTF-8编码读取
        with open(file_path, 'r', encoding='utf-8') as file:
            return file.read()
    except UnicodeDecodeError:
        # UTF-8失败，使用chardet检测编码
        with open(file_path, 'rb') as file:
            raw_data = file.read()
            
        # 检测编码
        detected = chardet.detect(raw_data)
        encoding = detected['encoding']
        
        if encoding is None:
            # 如果无法检测到编码，尝试一些常见编码
            common_encodings = ['gbk', 'gb2312', 'latin1']
            for enc in common_encodings:
                try:
                    return raw_data.decode(enc)
                except UnicodeDecodeError:
                    continue
            # 如果所有编码都失败，抛出异常
            raise Exception("无法确定文件编码")
        else:
            # 使用检测到的编码读取文件
            return raw_data.decode(encoding)

def remove_sql_comments(content):
    """
    移除SQL内容中的注释
    
    Args:
        content (str): 原始SQL内容
    
    Returns:
        str: 移除注释后的内容
    """
    # 移除多行注释 /* ... */
    content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
    
    # 移除单行注释 -- ...
    content = re.sub(r'--.*$', '', content, flags=re.MULTILINE)
    
    return content

def detect_sql_object_type(content):
    """
    根据SQL内容推测对象类型（表、视图、存储过程、函数等）
    
    Args:
        content (str): SQL内容
    
    Returns:
        dict: 包含对象类型和名称的字典
    """
    # 转换为小写便于匹配
    content_lower = content.lower()
    
    # 查找对象名称的正则表达式
    object_name_pattern = r'(?:table|view|procedure|function)\s+([^\s\(]+)'
    
    # 检测对象类型
    if re.search(r'\bcreate\s+table\b', content_lower):
        match = re.search(r'create\s+table\s+([^\s\(]+)', content_lower)
        return {
            'type': 'TABLE',
            'name': match.group(1) if match else None
        }
    elif re.search(r'\bcreate\s+view\b', content_lower):
        match = re.search(r'create\s+view\s+([^\s\(]+)', content_lower)
        return {
            'type': 'VIEW',
            'name': match.group(1) if match else None
        }
    elif re.search(r'\bcreate\s+procedure\b', content_lower):
        match = re.search(r'create\s+procedure\s+([^\s\(]+)', content_lower)
        return {
            'type': 'PROCEDURE',
            'name': match.group(1) if match else None
        }
    elif re.search(r'\bcreate\s+function\b', content_lower):
        match = re.search(r'create\s+function\s+([^\s\(]+)', content_lower)
        return {
            'type': 'FUNCTION',
            'name': match.group(1) if match else None
        }
    elif re.search(r'\bcreate\s+trigger\b', content_lower):
        match = re.search(r'create\s+trigger\s+([^\s\(]+)', content_lower)
        return {
            'type': 'TRIGGER',
            'name': match.group(1) if match else None
        }
    else:
        return {
            'type': 'UNKNOWN',
            'name': None
        }

def check_syntax_errors(content, result):
    """
    检查常见的SQL语法错误
    
    Args:
        content (str): SQL内容
        result (dict): 结果字典
    """
    # 检查括号是否匹配
    if content.count('(') != content.count(')'):
        result['is_valid'] = False
        result['errors'].append("括号不匹配")
    
    # 检查引号是否匹配
    if content.count("'") % 2 != 0:
        result['is_valid'] = False
        result['errors'].append("单引号不匹配")
    
    # 检查基本的CREATE语句结构
    create_statements = re.findall(r'create\s+(?:table|view|procedure|function)\s+', content.lower())
    if create_statements:
        # 检查是否有对应的AS或BEGIN关键字（对于存储过程和函数）
        if re.search(r'create\s+(?:procedure|function)', content.lower()):
            if not re.search(r'\b(as|begin)\b', content.lower()):
                result['errors'].append("存储过程或函数缺少AS或BEGIN关键字")

def check_keyword_errors(content, result):
    """
    检查SQL关键字使用错误
    
    Args:
        content (str): SQL内容
        result (dict): 结果字典
    """
    # 常见的SQL关键字错误
    reserved_words = [
        'select', 'from', 'where', 'insert', 'update', 'delete',
        'create', 'alter', 'drop', 'table', 'view', 'index',
        'procedure', 'function', 'trigger', 'primary', 'foreign',
        'key', 'constraint', 'unique', 'check', 'default'
    ]
    
    # 检查是否使用了保留字作为标识符（简单检查）
    # 这里只是一个基础示例，实际检查可能更复杂
    for word in reserved_words:
        # 检查保留字是否被用作对象名（简单模式）
        patterns = [
            rf'create\s+table\s+{word}\b',
            rf'create\s+view\s+{word}\b',
            rf'create\s+procedure\s+{word}\b',
            rf'create\s+function\s+{word}\b'
        ]
        
        for pattern in patterns:
            if re.search(pattern, content.lower()):
                result['errors'].append(f"使用了保留字 '{word}' 作为对象名")

def check_structure_integrity(content, result):
    """
    检查SQL结构完整性
    
    Args:
        content (str): SQL内容
        result (dict): 结果字典
    """
    # 检查是否有有效的SQL语句
    if not re.search(r'\b(create|alter|drop|select|insert|update|delete)\b', content.lower()):
        result['is_valid'] = False
        result['errors'].append("未检测到有效的SQL语句")
    
    # 检查CREATE TABLE语句的基本结构
    if re.search(r'\bcreate\s+table\b', content.lower()):
        # 检查是否有字段定义（至少有一对括号）
        if not re.search(r'create\s+table\s+[^\s\(]+\s*\(', content.lower()):
            result['errors'].append("CREATE TABLE语句缺少字段定义")
def create_submission_folder(base_path):
    """
    在指定路径下创建提交文件夹，命名格式为: yyyyMMdd_FENG
    
    Args:
        base_path (str): 基础路径
    
    Returns:
        str: 创建的文件夹路径，如果目录已存在则返回None
    """
    # 获取当前日期
    today = datetime.now()
    folder_name = today.strftime("%Y%m%d") + "_FENG"
    folder_path = os.path.join(base_path, folder_name)
    
    # 检查文件夹是否已存在
    if os.path.exists(folder_path):
        print(f"错误: 目标文件夹已存在: {folder_path}")
        print("为避免数据冲突，程序将终止执行。")
        return None
    
    # 如果文件夹不存在，则创建
    try:
        os.makedirs(folder_path)
        print(f"创建文件夹成功: {folder_path}")
        return folder_path
    except Exception as e:
        print(f"创建文件夹失败: {e}")
        return None


# 读取文档ID属性的示例代码
def read_document_id(file_path):
    """
    读取文件的文档ID属性
    
    Args:
        file_path (str): 文件路径
    
    Returns:
        str: 文档ID值，如果不存在则返回None
    """
    try:
        doc_id_stream_path = file_path + ":DocumentID"
        with open(doc_id_stream_path, 'r') as stream_file:
            return stream_file.read()
    except Exception as e:
        print(f"无法读取文档ID属性: {e}")
        return None

# 使用示例
# doc_id = read_document_id("path/to/your/file.sql")
# print(f"文档ID: {doc_id}")

def preserve_file_times(file_path, func):
    """
    执行函数的同时保持文件的时间戳不变
    
    Args:
        file_path (str): 文件路径
        func (callable): 要执行的函数
    """
    try:
        # 获取文件的原始时间戳
        stat_info = os.stat(file_path)
        access_time = stat_info.st_atime
        modify_time = stat_info.st_mtime
        
        # 执行函数
        func()
        
        # 恢复文件的时间戳
        os.utime(file_path, (access_time, modify_time))
        
    except Exception as e:
        print(f"保持文件时间戳失败: {e}")
        # 即使失败也执行原函数
        func()

def write_stream_preserve_time(file_path, stream_name, content):
    """
    写入备用数据流同时保持文件修改时间不变
    
    Args:
        file_path (str): 文件路径
        stream_name (str): 流名称
        content (str): 要写入的内容
    """
    def write_stream():
        stream_path = f"{file_path}:{stream_name}"
        with open(stream_path, 'w') as stream_file:
            stream_file.write(content)
    
    preserve_file_times(file_path, write_stream)

def copy_valid_files(valid_files, destination_folder):
    """
    将检查通过的SQL文件复制到目标文件夹，并添加自定义属性
    
    Args:
        valid_files (list): 检查通过的文件路径列表
        destination_folder (str): 目标文件夹路径
    
    Returns:
        list: 复制成功的文件列表
    """
    copied_files = []
    
    for file_path in valid_files:
        try:
            # 获取文件名
            file_name = os.path.basename(file_path)
            # 目标路径
            destination_path = os.path.join(destination_folder, file_name)
            # 复制文件
            shutil.copy2(file_path, destination_path)
            
            # 使用NTFS备用数据流存储文档ID属性（保持文件时间戳不变）
            try:
                # 写入备用数据流同时保持文件修改时间不变
                write_stream_preserve_time(destination_path, "DocumentID", "1234")
                print(f"已添加文档ID属性=1234到文件: {destination_path}")
                
                doc_id = read_document_id(destination_path)
                print(f"文档ID: {doc_id}")

                # 立即读取并打印文档ID属性
                try:
                    doc_id_stream_path = destination_path + ":DocumentID"
                    with open(doc_id_stream_path, 'r') as stream_file:
                        doc_id_value = stream_file.read()
                        print(f"已确认设置文档ID属性: {destination_path}, 文档ID值: {doc_id_value}")
                except Exception as read_error:
                    print(f"警告: 无法读取文档ID属性: {read_error}")
                    
            except Exception as stream_error:
                print(f"警告: 无法添加文档ID属性: {stream_error}")
            
            print(f"文件复制成功: {file_path} -> {destination_path}")
            copied_files.append(file_path)
        except Exception as e:
            print(f"文件复制失败: {file_path} - {e}")
    
    return copied_files


def rename_source_directories(valid_files):
    """
    重命名包含有效文件的源目录，去掉年份前两位
    
    Args:
        valid_files (list): 检查通过的文件路径列表
    """
    # 获取所有包含有效文件的源目录
    source_dirs = set()
    for file_path in valid_files:
        source_dir = os.path.dirname(file_path)
        source_dirs.add(source_dir)
    
    # 重命名每个源目录
    for source_dir in source_dirs:
        dir_name = os.path.basename(source_dir)
        # 检查目录名是否符合yyyyMMdd格式且长度至少为8位
        if len(dir_name) >= 8 and re.match(r'^\d{8}', dir_name):
            # 去掉年份前两位（前2个数字）
            new_dir_name = dir_name[2:]  # 去掉前2位
            new_dir_path = os.path.join(os.path.dirname(source_dir), new_dir_name)
            
            try:
                # 重命名目录
                os.rename(source_dir, new_dir_path)
                print(f"目录重命名成功: {source_dir} -> {new_dir_path}")
            except Exception as e:
                print(f"目录重命名失败: {source_dir} - {e}")
        else:
            print(f"目录名不符合格式，跳过重命名: {source_dir}")

# 更新主函数以使用analyze_sql_file
def find_valid_sql_files(base_directory):
    """
    查找指定目录下符合要求的SQL文件
    
    Args:
        base_directory (str): 基础目录路径
    
    Returns:
        list: 检查通过的文件路径列表
    """
    # 计算一周前的时间
    one_week_ago = datetime.now() - timedelta(weeks=1)
    
    # 用于存储检查通过的文件路径
    valid_files = []
    
    # 遍历base_directory下的所有目录
    for dir_name in os.listdir(base_directory):
        dir_path = os.path.join(base_directory, dir_name)
        
        # 检查是否为目录且以日期格式开头 (yyyyMMdd)
        if os.path.isdir(dir_path) and re.match(r'^\d{8}', dir_name):
            # 检查目录名是否包含需要排除的关键词
            if not re.search(r'(debug|bak|test)', dir_name, re.IGNORECASE):
                # 获取目录的最后修改时间
                try:
                    dir_time = datetime.fromtimestamp(os.path.getmtime(dir_path))
                except:
                    dir_time = None
                
                # 检查目录是否在最近一周内修改过
                if dir_time and dir_time >= one_week_ago:
                    print(f"检查目录: {dir_name} (修改时间: {dir_time.strftime('%Y-%m-%d %H:%M:%S')})")
                    
                    # 查找目录下的有效SQL文件
                    valid_files.extend(find_sql_files_in_directory(dir_path))
                else:
                    print(f"跳过目录(超出时间范围): {dir_name}")
    
    return valid_files
def find_sql_files_in_directory(dir_path):
    """
    在指定目录中查找有效的SQL文件
    
    Args:
        dir_path (str): 目录路径
    
    Returns:
        list: 检查通过的文件路径列表
    """
    valid_files = []
    
    # 遍历目录下的所有文件
    for file_name in os.listdir(dir_path):
        file_path = os.path.join(dir_path, file_name)
        
        # 检查是否为.sql文件且文件名不包含需要排除的关键词
        # 跳过大写的.SQL文件
        if file_name.lower().endswith('.sql') and not file_name.endswith('.SQL') and \
           not re.search(r'(debug|bak|test)', file_name, re.IGNORECASE):
            print(f"  发现SQL文件: {file_name}")
            # 分析SQL文件
            analysis_result = analyze_sql_file(file_path)
            if analysis_result['errors']:
                print(f"    警告: 发现 {len(analysis_result['errors'])} 个问题")
                for error in analysis_result['errors']:
                    print(f"      - {error}")
            else:
                print("    文件检查通过")
                valid_files.append(file_path)
        elif file_name.endswith('.SQL'):
            print(f"  跳过大写的.SQL文件: {file_name}")
    
    return valid_files
# 修改后的主函数和相关调用函数

def process_valid_files(valid_files, share_base_path, rename_source=False):
    """
    处理检查通过的SQL文件
    
    Args:
        valid_files (list): 检查通过的文件路径列表
        share_base_path (str): 共享目录路径
        rename_source (bool): 是否重命名源目录
    """
    print(f"\n共找到 {len(valid_files)} 个检查通过的文件")
    
    # 1. 创建提交文件夹
    submission_folder = create_submission_folder(share_base_path)
    
    # 如果创建文件夹失败（包括已存在的情况），则终止执行
    if submission_folder is None:
        print("错误: 无法创建提交文件夹，程序终止执行。")
        return
    
    print(f"已创建提交文件夹: {submission_folder}")
    
    # 2. 复制检查通过的文件到提交文件夹
    copied_files = copy_valid_files(valid_files, submission_folder)
    print(f"\n成功复制 {len(copied_files)} 个文件")
    
    # 3. 根据参数决定是否重命名源目录
    if rename_source:
        rename_source_directories(valid_files)
        print("源目录已重命名")
    else:
        print("根据参数设置，跳过源目录重命名")
    
    # 4. 报告结果
    print("\n=== 处理完成 ===")
    print(f"创建文件夹: {submission_folder}")
    print(f"复制文件数: {len(copied_files)}")
    if rename_source:
        print("源目录已重命名")

def list_sql_files(base_directory, share_base_path, rename_source=False):
    """
    列出指定目录下符合要求的SQL文件并处理
    
    Args:
        base_directory (str): SQL文件基础目录路径
        share_base_path (str): 共享目录路径
        rename_source (bool): 是否重命名源目录
    """
    # 查找有效的SQL文件
    valid_files = find_valid_sql_files(base_directory)
    
    # 如果有文件检查通过，执行后续操作
    if valid_files:
        process_valid_files(valid_files, share_base_path, rename_source)
    else:
        print("没有文件检查通过，无需执行后续操作")





# 使用示例
if __name__ == "__main__":
    # 创建参数解析器
    parser = argparse.ArgumentParser(description='SQL文件提交工具')
    parser.add_argument('--base-path', type=str, 
                        default=r"H:\\COMPANY\\xwork\\SQL\\MYWORK-ERP\\",
                        help='SQL文件基础目录路径')
    parser.add_argument('--share-path', type=str, 
                        default=r"H:\\COMPANY\\xwork\SQL\\00.MYWORK-ERP-WORKING\\",
                        help='共享目录路径')
    parser.add_argument('--rename-source', action='store_true',
                        help='是否在复制文件后重命名源目录（默认不重命名）')
    
    # 解析命令行参数
    args = parser.parse_args()
    
    # 使用传入的参数或默认值
    base_dir = args.base_path
    list_sql_files(base_dir, args.share_path, args.rename_source)